#include "Calculator.h"
#include "CalExcep.hpp"

using namespace std;
using namespace header;

namespace cal
{

header::Number Calculator::calculate(const std::string& expression) 
{
    istringstream iss(expression);
    TokenStream ts(iss);
    expr(ts);
    return _val;
}

void Calculator::expr(TokenStream& ts) 
{
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::LBRA: case globe::ADD: 
        case globe::SUB: case Token::tk_number:
        {
            term(ts);
            E(ts);
            break;
        }
        case globe::QUIT: 
        {
            _quit = true;
            break;
        }
        default: throw CalExcep{"bad expression"};
    }
}

void Calculator::E(TokenStream& ts) 
{
    Number tmp = _val;
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::ADD:
        {   
            ts.get();
            term(ts);
            _val = tmp + _val;
            E(ts);
            break;
        }
        case globe::SUB:
        {
            ts.get();
            term(ts);
            _val = tmp - _val;
            E(ts);
            break;
        }
        case globe::RBRA: case Token::tk_null: break;
        default: throw CalExcep{"lack of operator"};
    }
}

void Calculator::term(TokenStream& ts) 
{
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::LBRA: case globe::ADD: case globe::SUB: case Token::tk_number:
        {
            factor(ts);
            T(ts);
            break;
        }
        default: throw CalExcep{"bad term"};
    }
}

void Calculator::T(TokenStream& ts) 
{
    Number tmp = _val;
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::MUL:
        {
            ts.get();
            factor(ts);
            _val = tmp * _val;
            T(ts);
            break;
        }
        case globe::DIV:
        {
            ts.get();
            factor(ts);
            _val = tmp / _val;
            T(ts);
            break;
        }
        case globe::MOD:
        {
            ts.get();
            factor(ts);
            _val = tmp % _val;
            T(ts);
            break;
        }
        case globe::RBRA: case globe::ADD: 
        case globe::SUB: case Token::tk_null: break;
        default: throw CalExcep{"lack of operator"};
    }
}

void Calculator::factor(TokenStream& ts) 
{
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::LBRA: case globe::ADD: case globe::SUB: case Token::tk_number:
        {
            primary(ts);
            F(ts);
            break;
        }
        default: throw CalExcep{"bad factor"};
    }
}

void Calculator::F(TokenStream& ts) 
{
    Number tmp = _val;
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::POW:
        {
            ts.get();
            factor(ts);
            _val = tmp ^ _val;
            break;
        }
        case globe::ADD: case globe::SUB: case globe::MUL: case globe::DIV:
        case globe::MOD: case globe::RBRA: case Token::tk_null: break;
        default: throw CalExcep{"lack of operator"};
    }
}

void Calculator::primary(TokenStream& ts) 
{
    Number tmp = _val;
    auto peek = ts.peek();
    switch (peek.kind) {
        case globe::LBRA:
        {
            ts.get();
            expr(ts);
            if (ts.peek().kind != globe::RBRA) 
                throw CalExcep{"lack of right bracket: '" + string{globe::RBRA} + "'"};
            ts.get();
            break;
        }
        case globe::ADD:
        {
            ts.get();
            if (ts.peek().kind != Token::tk_number) 
                throw CalExcep{"lack of number"};
            _val = ts.get().val;
            break;
        }
        case globe::SUB:
        {
            ts.get();
            if (ts.peek().kind != Token::tk_number) 
                throw CalExcep{"lack of number"};
            _val = ts.get().val * "-1";
            break;
        }
        case Token::tk_number:
        {
            _val = ts.get().val;
            break;
        }
        default: throw CalExcep{"bad primary"};
    }
}

}   // namespace cal
